package com.uxwine.alarm;

import java.util.Calendar;

public class TimeCycle
{

	private static final long CYCLE_NONE  		= 0L;
	private static final long CYCLE_MONDAY 		= 1L << 0;
	private static final long CYCLE_TUESDAY 	= 1L << 1;
	private static final long CYCLE_WEDNESDAY	= 1L << 2;
	private static final long CYCLE_THURSDAY	= 1L << 3;
	private static final long CYCLE_FRIDAY 		= 1L << 4;
	private static final long CYCLE_SATURDAY	= 1L << 5;
	private static final long CYCLE_SUNDAY		= 1L << 6;
	private static final long CYCLE_MONTH  		= 1L << 7;
	private static final long CYCLE_YEAR  		= 1L << 8;
	private static final long CYCLE_DAY  		= 1L << 9;
	
	public static final long MASK_WEEKENDS 	 = CYCLE_SATURDAY | CYCLE_SUNDAY;
	public static final long MASK_WEEKDAYS 	 = CYCLE_MONDAY | CYCLE_TUESDAY | CYCLE_WEDNESDAY | CYCLE_THURSDAY | CYCLE_FRIDAY;
	public static final long MASK_WEEKALL	 = MASK_WEEKENDS | MASK_WEEKDAYS;
	public static final long MASK_EVERYDAY   = CYCLE_DAY;
	public static final long MASK_EVERYMONTH = CYCLE_MONTH;
	public static final long MASK_EVERYYEAR  = CYCLE_YEAR;
	public static final long MASK_NONE		 = CYCLE_NONE;
	
		
	private static int[] DAY_MAP = new int[] 
	{ 
		Calendar.MONDAY,
		Calendar.TUESDAY, 
		Calendar.WEDNESDAY, 
		Calendar.THURSDAY,
		Calendar.FRIDAY, 
		Calendar.SATURDAY, 
		Calendar.SUNDAY, 
	};

	// Bitmask of all repeating days
	private long mlCycle;

	public TimeCycle(long cycle)
	{
		mlCycle = cycle;
	}

	public long getCycle()
	{
		return mlCycle;
	}
	
	public void set(long mask)
	{
		mlCycle = mask;
	}

	public void set(TimeCycle dow)
	{
		mlCycle = dow.mlCycle;
	}

	public void setWeek(int day, boolean set)
	{
		mlCycle &= ~MASK_WEEKALL;
		
		if (set)
		{
			mlCycle |= (1 << day);
		} 
		else
		{
			mlCycle &= ~(1 << day);
		}
	}

	// Returns days of week encoded in an array of booleans.
	public boolean[] getBooleanArray()
	{
		boolean[] ret = new boolean[7];
		for (int i = 0; i < 7; i++)
		{
			ret[i] = isSet(i);
		}
		return ret;
	}

	public boolean isRepeatSet()
	{
		return mlCycle != 0;
	}
	
	public boolean isOnce()
	{
		return mlCycle == MASK_NONE;
	}
	
	public boolean isEveryYear()
	{
		return mlCycle == MASK_EVERYYEAR;
	}
	
	public boolean isEveryMonth()
	{
		return mlCycle == MASK_EVERYMONTH;
	}
	
	public boolean isEveryDay()
	{
		return mlCycle == MASK_EVERYDAY;
	}
	
	public Calendar getNextAlarm(TimeRecord time)
	{
		if(MASK_NONE == mlCycle)
		{
			//非周期的闹钟，返回一个非空即可
			final Calendar c = Calendar.getInstance();
			c.set(Calendar.YEAR, 0);
			return c;
		}
        
        //先搞定每年
        if(MASK_EVERYYEAR == mlCycle)
        {
        	return getByYear(time);        		
        }
        
        if(MASK_EVERYMONTH == mlCycle)
        {
        	return getByMonth(time);
        }
        
        return getByDay(time);
	}

	/**
	 * returns number of days from today until next alarm
	 * 
	 * @param c
	 *            must be set to today
	 */
	public int getNextAlarm(Calendar c)
	{
		//none
		if (mlCycle == MASK_NONE)
		{
			return -1;
		}

		int today = (c.get(Calendar.DAY_OF_WEEK) + 5) % 7;

		int day = 0;
		int dayCount = 0;
		for (; dayCount < 7; dayCount++)
		{
			day = (today + dayCount) % 7;
			if (isSet(day))
			{
				break;
			}
		}
		return dayCount;
	}
	
	public Calendar getByDay(TimeRecord time)
	{
		// start with now
        Calendar c = Calendar.getInstance();
        c.setTimeInMillis(System.currentTimeMillis());

        int hour = time.get(TimeRecord.HOUR);
        int minute = time.get(TimeRecord.MINUTE);
        int nowHour = c.get(Calendar.HOUR_OF_DAY);
        int nowMinute = c.get(Calendar.MINUTE);

        // if alarm is behind current time, advance one day
        if (hour < nowHour  ||
            hour == nowHour && minute <= nowMinute) {
            c.add(Calendar.DAY_OF_YEAR, 1);
        }
        c.set(Calendar.HOUR_OF_DAY, hour);
        c.set(Calendar.MINUTE, minute);
        c.set(Calendar.SECOND, 0);
        c.set(Calendar.MILLISECOND, 0);

        int addDays = getNextAlarm(c);
        if (addDays > 0)
        	c.add(Calendar.DAY_OF_WEEK, addDays);
        return c;
	}
	
	private Calendar getByMonth(TimeRecord time)
	{
		//zzm:下面计算一下周期的闹钟
        Calendar now = Calendar.getInstance();
        now.setTimeInMillis(System.currentTimeMillis());
        Calendar dist = Calendar.getInstance();  

        int day = time.get(TimeRecord.DATE);
        int hour = time.get(TimeRecord.HOUR);
        int min = time.get(TimeRecord.MINUTE);
        
        int nowYear = now.get(Calendar.YEAR);
        int nowMonth = now.get(Calendar.MONTH);
        
        
        setWithCheckMonth(dist, nowYear, nowMonth, day, hour, min);
        
        if(dist.before(now))
        {
        	if(nowMonth == 12)
        	{
        		nowYear++;
        		nowMonth = 1;
        	}
        	setWithCheckMonth(dist, nowYear, nowMonth, day, hour, min);
        }
        
        return dist;
	}
	
	private static void setWithCheckMonth(Calendar c, int y, int m, int d, int h, int mm)
	{		
		//排除了2.29, 2.30, 2.31的情况
		if(m == 2 && d >= 29)
		{
			d = 29;
			setWithLeap(c, y, m, d, h, mm);
			return;
		}
		
		if((d == 31) && (m == 4 || m == 6 || m == 9 || m == 11))
		{
			d = 30;		
		}
		c.set(y, m, d, h, mm);
	}
	
	private Calendar getByYear(TimeRecord time)
	{
		//zzm:下面计算一下周期的闹钟
        Calendar now = Calendar.getInstance();
        now.setTimeInMillis(System.currentTimeMillis());
        Calendar dist = Calendar.getInstance();        

        int month = time.get(TimeRecord.YEAR);
        int day = time.get(TimeRecord.DATE);
        int hour = time.get(TimeRecord.HOUR);
        int min = time.get(TimeRecord.MINUTE);
        
        int nowYear = now.get(Calendar.YEAR);
        setWithLeap(dist, nowYear, month, day, hour, min);
        
        if(dist.before(now))
        	setWithLeap(dist, nowYear + 1, month, day, hour, min);
        
        return dist;
	}
	
	private static void setWithLeap(Calendar c, int y, int m, int d, int h, int mm)
	{
		c.set(y, m, ((!isLeap(y)) && (m==2 && d == 29)) ? 28 : d, h, m, 0);
	}
	
	public static boolean isLeap(int year)
	{
		return ((year%4==0)&&(year%100!=0)||(year%400==0));
	}


	public boolean isSet(int day)
	{
		if(mlCycle == MASK_EVERYDAY)
			return true;
		return ((mlCycle & (1 << day)) > 0);
	}
}
